﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using Autofac;
using System.Web.Compilation;
using System.Text.RegularExpressions;

namespace Laugh.Environment
{
    public static class TypeSearcher
    {
        private static List<Assembly> _cacheAssembly = new List<Assembly>();
        private static List<Type> _cahceType = new List<Type>();

        private static string AppBaseDir
        {
            get
            {
                var path = AppDomain.CurrentDomain.BaseDirectory;
                if (path.Contains("bin"))
                    return path;
                else
                    return path + "bin";
            }
        }

        public static void FindAndInjectionTypes(ContainerBuilder builder)
        {
            var dependencies = _cahceType.Count > 0 ? _cahceType : null;

            if (dependencies == null) dependencies = GetTypesFrom(typeof(IDependency)).ToList();
            if (dependencies == null) return;

            dependencies.ForEach(type =>
            {
                if (!type.IsGenericType)
                {
                    var registration = builder.RegisterType(type).InstancePerLifetimeScope();

                    type.GetInterfaces().Each(interfaceType =>
                    {
                        registration = registration.As(interfaceType);
                        if (typeof(ISingletonDependency).IsAssignableFrom(type))
                        {
                            registration = registration.SingleInstance();
                        }
                        else if (typeof(IUnitOfWorkDependency).IsAssignableFrom(interfaceType))
                        {
                            //registration = registration.InstancePerHttpRequest();
                        }
                        else if (typeof(ITransientDependency).IsAssignableFrom(interfaceType))
                        {
                            registration = registration.InstancePerDependency();
                        }
                    });
                }
                else
                {
                    var registration = builder.RegisterGeneric(type).InstancePerLifetimeScope();

                    type.GetInterfaces().Where(a => a.IsGenericType).Each(interfaceType =>
                    {
                        registration = registration.As(interfaceType);
                        if (typeof(ISingletonDependency).IsAssignableFrom(type))
                        {
                            registration = registration.SingleInstance();
                        }
                        else if (typeof(IUnitOfWorkDependency).IsAssignableFrom(interfaceType))
                        {
                            //registration = registration.InstancePerHttpRequest();
                        }
                        else if (typeof(ITransientDependency).IsAssignableFrom(interfaceType))
                        {
                            registration = registration.InstancePerDependency();
                        }
                    });
                }
            });
        }

        public static IEnumerable<Type> GetTypesFrom(Type type)
        {
            var assemblies = BaseDirectoryAssemblies();

            return assemblies.ConcreteTypes().Where(a => type.IsAssignableFrom(a) && !a.IsInterface && a.IsPublic && !a.IsAbstract);
        }

        public static IEnumerable<Type> GetTypesByGenericType(Type type)
        {
            if (!type.IsGenericTypeDefinition)
                throw new ArgumentException("type 不是泛型类型");

            var assemblies = BaseDirectoryAssemblies();

            return assemblies.ConcreteTypes().Where(a => a.IsAssignableByGenericType(type));
        }

        /// <summary>
        /// 获取WEB应用程序编译时所需求直接引用的程序集列表
        /// 注意：间接引用程序集，如果在程序没有使用程序集内的任何对象，则不会加载到列表中
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Assembly> BuildReferencedAssemblies()
        {
            if (_cacheAssembly != null && _cacheAssembly.Count() > 0) return _cacheAssembly;

            _cacheAssembly = BuildManager.GetReferencedAssemblies().OfType<Assembly>()
                        .Concat(AppDomain.CurrentDomain.GetAssemblies())
                        .Distinct().Where(i => !i.FullName.StartsWith("system", true, null)
                            && !i.FullName.StartsWith("NHibernate", true, null)
                            && !i.FullName.StartsWith("Autofac", true, null)
                            && !i.FullName.StartsWith("log4net", true, null)
                            && !i.FullName.StartsWith("microsoft", true, null)
                            && !i.FullName.StartsWith("mscorlib", true, null)
                            && !i.IsDynamic)
                        .OrderBy(o => o.FullName).ToList();

            return _cacheAssembly;
        }

        /// <summary>
        /// 扫描WEB应用程序基本目录(bin目录)下的DLL文件加载到当前应用程序中，并且返回应用程序集列表
        /// 注意：可能会扫描到其它非相关的DLL
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Assembly> BaseDirectoryAssemblies(string pattern = null)
        {
            List<Assembly> list = new List<Assembly>();
            string[] files = System.IO.Directory.GetFiles(AppBaseDir, "*.DLL", System.IO.SearchOption.TopDirectoryOnly);
            string basePattern = @"Autofac|Iesi\.Collections|log4net|NHibernate|System.";

            var needFiles = files.Where(f =>
            {
                var name = System.IO.Path.GetFileNameWithoutExtension(f);

                if (Regex.IsMatch(name, basePattern, RegexOptions.IgnoreCase))
                    return false;

                if (pattern != null && Regex.IsMatch(name, pattern, RegexOptions.IgnoreCase))
                    return false;
                else
                    return true;
            });

            foreach (var dllPath in needFiles)
            {
                try
                {
                    var an = AssemblyName.GetAssemblyName(dllPath);
                    var ass = AppDomain.CurrentDomain.Load(an);
                    list.Add(ass);

                }
                catch (BadImageFormatException ex)
                {
                    throw new Exception("加载程序集[{0}]失败,错误:{1}".Format2(dllPath, ex.Message));
                }
            }

            return list;
        }
    }
}
